﻿Imports System.Text

Public Class PlaybackLabel
  Private _backgroundBmp As Bitmap = Nothing
  Private _smallMode As Boolean = False
  Private _dpiFactor As Single = 0.0F
  Private _smallModeOffset As Integer = 0
  Private _lastMousePosX As Integer = 0
  Private _lastMousePosY As Integer = 0
  Private _controlHeight As Integer = 120
  Private _topheight As Integer = 0
  Private _bottomBMPTop As Bitmap = Nothing
  Private _bottomBMPBottom As Bitmap = Nothing
  Private _bottomBMPHLTop As Bitmap = Nothing
  Private _bottomBMPHLBottom As Bitmap = Nothing
  Private _memorizedIcon As Bitmap = Nothing
  Private _bottomFGColorBrush As New SolidBrush(Color.FromArgb(65, 255, 65))
  Private _brFg As New SolidBrush(Me.ForeColor)
  Private _brFgOverlay As New SolidBrush(Me.ForeColor)
  Private _brFgHugeOverlay As New SolidBrush(Me.ForeColor)
  Private _brLongIndicator As New SolidBrush(Me.ForeColor)
  Private _isOverlayHuge As Boolean = False
  Private _largeDisplayHeight As Integer = 0
  Private _largeDisplayOffset As Integer = 0
  Private _largeDisplayScrollDirectionDown As Boolean = True
  Private _longPressPercentage As Single = 0.0F

  Private _bufwidth As Integer = 0
  Private _bufheight As Integer = 0
  Private _buffer As Bitmap = Nothing
  Private _bufrect As Rectangle = Rectangle.Empty
  Private _brbg As SolidBrush
  Private _strformat As New StringFormat
  Private _lgheight As Integer = 0
  Private _smheight As Integer = 0
  Private _blheight As Integer = 0
  Private _previouslargedisplayheight As Integer = 0
  Private _hFnt As Font = Nothing
  Private _bFnt As Font = Nothing
  Private _nFnt As Font = Nothing
  Private _sFnt As Font = Nothing
  Private _tFnt As Font = Nothing

  Private _artist As String = ""
  Private _title As String = ""
  Private _overlay As String = ""
  Private _isReady As Boolean = False
  Private _showMemorizeIcon As Boolean = False

  Private _trackTime As New TimeSpan(0)
  Private _trackTimeAlt As New TimeSpan(0, 0, -1)
  Private _trackTimeNeg As New TimeSpan(0)
  Private _trackLength As New TimeSpan(0)
  Private _statusMsg As String = ""

  Private _resetme As Boolean = False
  Private _hugeFontSize As Single = 26
  Private _largeFontSize As Single = 12.5
  Private _smallFontSize As Single = 11.5
  Private _barFontSize As Single = 8.5
  Private _smallHeight As Integer = 0


  Public Overrides Property ForeColor() As System.Drawing.Color
    Get
      Return MyBase.ForeColor
    End Get
    Set(ByVal value As System.Drawing.Color)
      MyBase.ForeColor = value
      Me._brFg = New SolidBrush(value)
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property OverlayForeColor() As Color
    Get
      Return Me._brFgOverlay.Color
    End Get
    Set(ByVal value As Color)
      Me._brFgOverlay = New SolidBrush(value)
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property HugeOverlayForeColor() As Color
    Get
      Return Me._brFgHugeOverlay.Color
    End Get
    Set(ByVal value As Color)
      Me._brFgHugeOverlay = New SolidBrush(value)
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property BottomStatusStripForeColor() As Color
    Get
      Return Me._bottomFGColorBrush.Color
    End Get
    Set(ByVal value As Color)
      Me._bottomFGColorBrush = New SolidBrush(value)
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property LongPressIndicatorColor() As Color
    Get
      Return Me._brLongIndicator.Color
    End Get
    Set(ByVal value As Color)
      Me._brLongIndicator = New SolidBrush(value)
      Me.InvalidateSmart()
    End Set
  End Property

  Public Overrides Property BackColor() As System.Drawing.Color
    Get
      Return MyBase.BackColor
    End Get
    Set(ByVal value As System.Drawing.Color)
      MyBase.BackColor = value
      Me._brbg = New SolidBrush(value)
    End Set
  End Property

  Public Sub New()
    InitializeComponent()
    Me._dpiFactor = Me.AutoScaleDimensions.Height / 192.0F
    Me._smallHeight = 76 * Me._dpiFactor
    Me.BackColor = Color.Black
  End Sub

  ''' <summary>
  ''' Resets the default size of the control
  ''' </summary>
  ''' <param name="newSize">Negative means the control is "small"; 0 means it's normal; positive means it's "large"</param>
  Public Sub SetDefaultControlHeight(Optional ByVal newSize As Integer = 0)
    If newSize > 0 Then
      Me._hugeFontSize = 28
      Me._largeFontSize = 13.5
      Me._smallFontSize = 12.5
      Me._barFontSize = 9
      Me._smallHeight = 84 * Me._dpiFactor
    ElseIf newSize < 0 Then
      Me._hugeFontSize = 24
      Me._largeFontSize = 11.5
      Me._smallFontSize = 10.5
      Me._barFontSize = 8
      Me._smallHeight = 68 * Me._dpiFactor
    Else
      Me._hugeFontSize = 26
      Me._largeFontSize = 12.5
      Me._smallFontSize = 11.5
      Me._barFontSize = 8.5
      Me._smallHeight = 76 * Me._dpiFactor
    End If

    Me._controlHeight = Me.Height
    Me._resetme = True
    Me.SmallMode = Me.SmallMode ' Calling this property forces a reset of the display size
    Me.InvalidateSmart()
  End Sub

  Public Property IsReady() As Boolean
    Get
      Return Me._isReady
    End Get
    Set(ByVal value As Boolean)
      Me._isReady = value
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property ShowMemorizeIcon() As Boolean
    Get
      Return Me._showMemorizeIcon
    End Get
    Set(ByVal value As Boolean)
      Me._showMemorizeIcon = value
      Me.InvalidateSmart()
    End Set
  End Property

  Public ReadOnly Property ActualScaleFactor()
    Get
      Return Me._dpiFactor
    End Get
  End Property


  Public Property Artist() As String
    Get
      Return Me._artist
    End Get
    Set(ByVal value As String)
      Me._artist = value.Trim
      Me.ResetScrollTimer()
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property Title() As String
    Get
      Return Me._title
    End Get
    Set(ByVal value As String)
      Me._title = value.Trim
      Me.ResetScrollTimer()
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property OverlayText() As String
    Get
      Return Me._overlay
    End Get
    Set(ByVal value As String)
      Me._isOverlayHuge = False
      Me._overlay = value.Trim
      Me.ResetScrollTimer()
      Me.InvalidateSmart()
    End Set
  End Property

  Public WriteOnly Property OverlayTextHuge() As String
    Set(ByVal value As String)
      Me._isOverlayHuge = True
      Me._overlay = value.Trim
      Me.ResetScrollTimer()
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property SmallMode() As Boolean
    Get
      Return Me._smallMode
    End Get
    Set(ByVal value As Boolean)
      Me._smallMode = value
      If Me._smallMode Then
        Me.Height = Me._smallHeight
      Else
        Me.Height = Me._controlHeight
      End If
      Me.ResetScrollTimer()
      Me.InvalidateSmart()
    End Set
  End Property

  ' Track time in seconds
  Public Property TrackTime() As Integer
    Get
      Return Me._trackTime.TotalSeconds
    End Get
    Set(ByVal value As Integer)
      Me._trackTime = New TimeSpan(0, 0, value)
      Me.UpdateNegativeTimer()
      Me.InvalidateSmart()
    End Set
  End Property

  ' Track time in seconds
  Public Property TrackTimeAlternate() As Integer
    Get
      Return Me._trackTimeAlt.TotalSeconds
    End Get
    Set(ByVal value As Integer)
      Me._trackTimeAlt = New TimeSpan(0, 0, value)
      Me.InvalidateSmart()
    End Set
  End Property

  ' Track length in seconds
  Public Property TrackLength() As Integer
    Get
      Return Me._trackLength.TotalSeconds
    End Get
    Set(ByVal value As Integer)
      Me._trackLength = New TimeSpan(0, 0, value)
      Me.UpdateNegativeTimer()
      Me.InvalidateSmart()
    End Set
  End Property

  Private Sub UpdateNegativeTimer()
    If Me._trackLength.TotalSeconds > 0 AndAlso Me._trackLength > Me._trackTime Then
      Me._trackTimeNeg = Me._trackLength.Subtract(Me._trackTime)
    Else
      Me._trackTimeNeg = New TimeSpan(0, 0, -1)
    End If
  End Sub

  Public Property StatusMessage() As String
    Get
      Return Me._statusMsg
    End Get
    Set(ByVal value As String)
      Me._statusMsg = value.Trim
      Me.InvalidateSmart()
    End Set
  End Property

  Public Sub CollapseControl()
    Me.Height = 0
  End Sub

  Private Sub ResetScrollTimer(Optional ByVal resetOffset As Boolean = True)
    Me.TimerScroll.Enabled = False

    If resetOffset Then
      Me._smallModeOffset = 0
      Me._largeDisplayOffset = 0
    End If

    If Me._smallMode OrElse Me._largeDisplayHeight > 0 Then
      Me.TimerScroll.Interval = 5000
      Me.TimerScroll.Enabled = True
    End If
  End Sub

  Public Property BackgroundBitmap() As Bitmap
    Get
      Return Me._backgroundBmp
    End Get
    Set(ByVal value As Bitmap)
      Try : If Me._backgroundBmp IsNot Nothing Then : Me._backgroundBmp.Dispose() : End If : Catch : End Try
      Me._backgroundBmp = value
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property MemorizedIconBitmap() As Bitmap
    Get
      Return Me._memorizedIcon
    End Get
    Set(ByVal value As Bitmap)
      Try : If Me._memorizedIcon IsNot Nothing Then : Me._memorizedIcon.Dispose() : End If : Catch : End Try
      Me._memorizedIcon = value
      Me.InvalidateSmart()
    End Set
  End Property

  Public Property StatusBarTopBitmap() As Bitmap
    Get
      Return Me._bottomBMPTop
    End Get
    Set(ByVal value As Bitmap)
      Try : If Me._bottomBMPTop IsNot Nothing Then : Me._bottomBMPTop.Dispose() : End If : Catch : End Try

      If value IsNot Nothing Then
        If Me._dpiFactor <> 1.0F Then
          Me._bottomBMPTop = New Bitmap(value.Width * Me._dpiFactor, value.Height * Me._dpiFactor)
          Using g As Graphics = Graphics.FromImage(Me._bottomBMPTop)
            g.DrawImage(value, New Rectangle(0, 0, Me._bottomBMPTop.Width, Me._bottomBMPTop.Height), New Rectangle(0, 0, value.Width, value.Height), GraphicsUnit.Pixel)
          End Using
        Else
          Me._bottomBMPTop = value
        End If
      Else
        Me._bottomBMPTop = Nothing
      End If

      Me.InvalidateSmart()
    End Set
  End Property

  Public Property StatusBarBottomBitmap() As Bitmap
    Get
      Return Me._bottomBMPBottom
    End Get
    Set(ByVal value As Bitmap)
      Try : If Me._bottomBMPBottom IsNot Nothing Then : Me._bottomBMPBottom.Dispose() : End If : Catch : End Try

      If value IsNot Nothing Then
        If Me._dpiFactor <> 1.0F Then
          Me._bottomBMPBottom = New Bitmap(value.Width * Me._dpiFactor, value.Height * Me._dpiFactor)
          Using g As Graphics = Graphics.FromImage(Me._bottomBMPBottom)
            g.DrawImage(value, New Rectangle(0, 0, Me._bottomBMPBottom.Width, Me._bottomBMPBottom.Height), New Rectangle(0, 0, value.Width, value.Height), GraphicsUnit.Pixel)
          End Using
        Else
          Me._bottomBMPBottom = value
        End If
      Else
        Me._bottomBMPBottom = Nothing
      End If

      Me.InvalidateSmart()
    End Set
  End Property

  Public Property StatusBarHighlightTopBitmap() As Bitmap
    Get
      Return Me._bottomBMPHLTop
    End Get
    Set(ByVal value As Bitmap)
      Try : If Me._bottomBMPHLTop IsNot Nothing Then : Me._bottomBMPHLTop.Dispose() : End If : Catch : End Try

      If value IsNot Nothing Then
        If Me._dpiFactor <> 1.0F Then
          Me._bottomBMPHLTop = New Bitmap(value.Width * Me._dpiFactor, value.Height * Me._dpiFactor)
          Using g As Graphics = Graphics.FromImage(Me._bottomBMPHLTop)
            g.DrawImage(value, New Rectangle(0, 0, Me._bottomBMPHLTop.Width, Me._bottomBMPHLTop.Height), New Rectangle(0, 0, value.Width, value.Height), GraphicsUnit.Pixel)
          End Using
        Else
          Me._bottomBMPHLTop = value
        End If
      Else
        Me._bottomBMPHLTop = Nothing
      End If

      Me.InvalidateSmart()
    End Set
  End Property

  Public Property StatusBarHighlightBottomBitmap() As Bitmap
    Get
      Return Me._bottomBMPHLBottom
    End Get
    Set(ByVal value As Bitmap)
      Try : If Me._bottomBMPHLBottom IsNot Nothing Then : Me._bottomBMPHLBottom.Dispose() : End If : Catch : End Try

      If value IsNot Nothing Then
        If Me._dpiFactor <> 1.0F Then
          Me._bottomBMPHLBottom = New Bitmap(value.Width * Me._dpiFactor, value.Height * Me._dpiFactor)
          Using g As Graphics = Graphics.FromImage(Me._bottomBMPHLBottom)
            g.DrawImage(value, New Rectangle(0, 0, Me._bottomBMPHLBottom.Width, Me._bottomBMPHLBottom.Height), New Rectangle(0, 0, value.Width, value.Height), GraphicsUnit.Pixel)
          End Using
        Else
          Me._bottomBMPHLBottom = value
        End If
      Else
        Me._bottomBMPHLBottom = Nothing
      End If

      Me.InvalidateSmart()
    End Set
  End Property

  Public Property LongPressPercentage() As Single
    Get
      Return Me._longPressPercentage
    End Get
    Set(ByVal value As Single)
      If value > 1.0F Then
        value = 1.0F
      ElseIf value < 0.0F Then
        value = 0.0F
      End If
      Me._longPressPercentage = value
      Me.InvalidateSmart()
    End Set
  End Property


  Protected Overrides Sub OnPaint(ByVal e As System.Windows.Forms.PaintEventArgs)
    Try
      If Me._resetme OrElse Me._buffer Is Nothing OrElse Me.Width <> Me._bufwidth OrElse Me.Height <> Me._bufheight OrElse Me._bFnt Is Nothing Then
        ' flag that we reset the me._buffer (for next if statement)
        Me._resetme = True

        ' Setup back me._buffer
        Me._bufwidth = Me.Width
        Me._bufheight = Me.Height

        If Me._buffer IsNot Nothing Then
          Me._buffer.Dispose()
        End If

        ' me._buffer
        Me._buffer = New Bitmap(Me._bufwidth, Me._bufheight)
        Me._bufrect = New Rectangle(0, 0, Me._bufwidth, Me._bufheight)
      End If

      ' Create temporary graphics me._buffer
      Dim g As Graphics = Graphics.FromImage(Me._buffer)

      If Me._resetme Then
        Me._resetme = False ' Now unset the resetme._buffer flag

        ' Box heights
        Me._lgheight = Me._controlHeight
        Me._smheight = Me._controlHeight - (120 * Me._dpiFactor)

        ' Set up fonts
        Me._hFnt = New Font(Me.Font.Name, Me._hugeFontSize, FontStyle.Bold)
        Me._bFnt = New Font(Me.Font.Name, Me._largeFontSize, FontStyle.Bold)
        Me._nFnt = New Font(Me.Font.Name, Me._largeFontSize, FontStyle.Regular)
        Me._sFnt = New Font(Me.Font.Name, Me._smallFontSize, FontStyle.Regular)
        Me._tFnt = New Font(Me.Font.Name, Me._barFontSize, FontStyle.Regular)
        Me._strformat.FormatFlags = 0
        Me._strformat.Alignment = StringAlignment.Center
        Me._strformat.LineAlignment = StringAlignment.Near

        ' Set up bottom line height
        Me._blheight = g.MeasureString("Pp", Me._tFnt).Height + (2 * Me._dpiFactor)

        ' And the rest of the height
        Me._topheight = Me._bufrect.Height - Me._blheight
      End If

      ' Erase background
      g.FillRectangle(Me._brbg, 0, 0, Me._bufwidth, Me._bufheight)

      If Me._isReady Then
        ' Draw backgrond image (if we have one)
        If Me._backgroundBmp IsNot Nothing Then
          Dim sRect As New Rectangle(0, 0, Me._backgroundBmp.Width, Me._backgroundBmp.Height)
          Dim dRect As New Rectangle(0, 0, Me._backgroundBmp.Width * Me._dpiFactor, Me._backgroundBmp.Height * Me._dpiFactor)
          g.DrawImage(Me._backgroundBmp, dRect, sRect, GraphicsUnit.Pixel)
        End If

        ' Draw the "long press" animation (if applicable)
        If Me._longPressPercentage > 0.0F AndAlso Me._longPressPercentage <= 1.0F Then
          g.FillRectangle(Me._brLongIndicator, 0, 0, Me._bufwidth * Me._longPressPercentage, 6 * Me._dpiFactor)
        End If

        If Me._showMemorizeIcon AndAlso Me._memorizedIcon IsNot Nothing Then
          Dim mw = Me._memorizedIcon.Width * Me._dpiFactor
          Dim mh = Me._memorizedIcon.Height * Me._dpiFactor
          g.DrawImage(Me._memorizedIcon, New Rectangle(Me._bufwidth - mw, 0, mw, mh), New Rectangle(0, 0, Me._memorizedIcon.Width, Me._memorizedIcon.Height), GraphicsUnit.Pixel)
        End If

        If Me._overlay.Length > 0 Then       ' ------------------ DRAW OVERLAY ------------------ 
          If Me._smallMode Then
            With g.MeasureString(Me._overlay, Me._sFnt)
              Dim y As Integer = (Me._topheight - .Height) >> 1
              Dim x As Integer = (Me._bufwidth - .Width) >> 1
              If x < 0 Then x = 0
              g.DrawString(Me._overlay, Me._sFnt, Me._brFgOverlay, x, y)
            End With
          ElseIf Me._isOverlayHuge Then
            With MeasureString(g, Me._overlay, Me._bufrect, Me._hFnt)
              Dim y As Integer = (Me._topheight - .Height) >> 1
              If y < 0 Then y = 0
              Dim r As New Rectangle(Me._bufrect.X, y, Me._bufrect.Width, .Height)
              g.DrawString(Me._overlay, Me._hFnt, Me._brFgHugeOverlay, r, Me._strformat)
            End With
          Else
            With MeasureString(g, Me._overlay, Me._bufrect, Me._bFnt)
              Dim y As Integer = (Me._topheight - .Height) >> 1
              If y < 0 Then y = 0
              Dim r As New Rectangle(Me._bufrect.X, y, Me._bufrect.Width, .Height)
              g.DrawString(Me._overlay, Me._bFnt, Me._brFgOverlay, r, Me._strformat)
            End With
          End If
        ElseIf Me._smallMode Then            ' ------------------ DRAW SMALL MODE ------------------ 
          Dim fullTitle As String = ""

          If Me._artist.Trim.Length > 0 Then
            If Me._title.Trim.Length > 0 Then
              fullTitle = Me._artist & " - " & Me._title  ' Concat the strings
            Else
              fullTitle = Me._artist
            End If
          Else
            fullTitle = Me._title
          End If

          Dim sSize As SizeF = g.MeasureString(fullTitle, Me._sFnt)  ' Measure the text items

          If sSize.Width > Me.Width Then
            If Me._smallModeOffset > 0 Then
              Me._smallModeOffset = 0
            ElseIf Me._smallModeOffset < 0 - sSize.Width Then
              If Me.TimerScroll.Enabled Then
                Me.ResetScrollTimer()
                Me._smallModeOffset = 0
              Else
                Me._smallModeOffset = 0 - sSize.Width
              End If
            End If
          Else
            Me._smallModeOffset = 0
            Me.TimerScroll.Enabled = False
          End If

          g.DrawString(fullTitle, Me._sFnt, Me._brFg, Me._smallModeOffset, (Me._topheight - sSize.Height) >> 1)  ' Draw the string!
        Else                                  ' ------------------ DRAW LARGE MODE ------------------ 
          If Me._artist.Length > 0 AndAlso Me._title.Length > 0 Then
            ' Measure the text items
            Dim aSize As Size = MeasureString(g, Me._artist, Me._bufrect, Me._bFnt)
            Dim tSize As Size = MeasureString(g, Me._title, Me._bufrect, Me._nFnt)
            Dim aRectF As RectangleF
            Dim tRectF As RectangleF

            Me._largeDisplayHeight = aSize.Height + tSize.Height

            If Me._largeDisplayHeight < Me._topheight Then
              ' Center the track title vertically, but show the artist always aligned on the top
              aRectF = New RectangleF(0, 0, Me._bufwidth, aSize.Height)
              tRectF = New RectangleF(0, aSize.Height + ((Me._topheight - (aSize.Height + tSize.Height)) >> 1), Me._bufwidth, tSize.Height)

              ' Reset vertical scrolling counters
              Me._previouslargedisplayheight = 0
              Me._largeDisplayHeight = 0
            Else
              aRectF = New RectangleF(0, Me._largeDisplayOffset, Me._bufwidth, aSize.Height)
              tRectF = New RectangleF(0, aSize.Height + Me._largeDisplayOffset + (2 * Me._dpiFactor), Me._bufwidth, tSize.Height)

              ' Set up for vertical scrolling
              If Me._previouslargedisplayheight = 0 Then
                Me._previouslargedisplayheight = Me._largeDisplayHeight
                Me.ResetScrollTimer(False)
              End If
            End If

            g.DrawString(Me._artist, Me._bFnt, Me._brFg, aRectF, Me._strformat)
            g.DrawString(Me._title, Me._nFnt, Me._brFg, tRectF, Me._strformat)
          Else
            Dim sngstr As String = ""

            If Me._artist.Length > 0 Then
              sngstr = Me._artist
            Else
              sngstr = Me._title
            End If

            With MeasureString(g, sngstr, Me._bufrect, Me._sFnt)
              Dim r As New Rectangle(Me._bufrect.X, (Me._topheight - .Height) >> 1, Me._bufrect.Width, .Height)
              g.DrawString(sngstr, Me._sFnt, Me._brFg, r, Me._strformat)
            End With
          End If
        End If

        ' Draw bottom status bar stuff
        Dim timerString As String = GetTimerString()
        Dim blpadding As Integer = 10 * Me._dpiFactor
        Dim blY As Integer = Me._bufrect.Height - Me._blheight
        Dim blTW As Integer = g.MeasureString(timerString, Me._tFnt).Width + (30 * Me._dpiFactor)

        ' Draw the status bar background
        If Me._bottomBMPTop IsNot Nothing AndAlso Me._bottomBMPBottom IsNot Nothing Then
          g.DrawImage(Me._bottomBMPTop, New Rectangle(0, blY, Me._bufwidth, Me._blheight), New Rectangle(0, 0, Me._bottomBMPTop.Width, Me._bottomBMPTop.Height), GraphicsUnit.Pixel)
          g.DrawImage(Me._bottomBMPBottom, New Rectangle(0, Me._bufrect.Height - Me._bottomBMPBottom.Height, Me._bufwidth, Me._bottomBMPBottom.Height), New Rectangle(0, 0, Me._bottomBMPBottom.Width, Me._bottomBMPBottom.Height), GraphicsUnit.Pixel)

          If Me._bottomBMPHLTop IsNot Nothing AndAlso Me._bottomBMPHLBottom IsNot Nothing AndAlso Me._trackLength.TotalSeconds > 0.0R Then
            Dim hlw As Integer = CInt(CDbl(Me._bufwidth) * (Me._trackTime.TotalSeconds / Me._trackLength.TotalSeconds))
            g.DrawImage(Me._bottomBMPHLTop, New Rectangle(0, blY, hlw, Me._blheight), New Rectangle(0, 0, Me._bottomBMPHLTop.Width, Me._bottomBMPHLTop.Height), GraphicsUnit.Pixel)
            g.DrawImage(Me._bottomBMPHLBottom, New Rectangle(0, Me._bufrect.Height - Me._bottomBMPHLBottom.Height, hlw, Me._bottomBMPHLBottom.Height), New Rectangle(0, 0, Me._bottomBMPHLBottom.Width, Me._bottomBMPHLBottom.Height), GraphicsUnit.Pixel)
          End If
        Else
          g.FillRectangle(New SolidBrush(Color.Black), 0, blY, Me._bufrect.Width, Me._blheight)
        End If

        ' Draw the status message
        g.DrawString(timerString, Me._tFnt, Me._bottomFGColorBrush, blpadding, blY)
        If Me._statusMsg.Length > 0 Then
          With g.MeasureString(Me._statusMsg, Me._tFnt)
            Dim blSX As Integer = Me._bufrect.Width - (blpadding + .Width)
            If blSX > blTW Then
              g.DrawString(Me._statusMsg, Me._tFnt, Me._bottomFGColorBrush, blSX, blY)
            Else
              g.DrawString(Me._statusMsg, Me._tFnt, Me._bottomFGColorBrush, blTW, blY)
            End If
          End With
        End If
      End If

      g.Dispose()
      e.Graphics.DrawImage(Me._buffer, 0, 0)
    Catch ex As Exception
      ErrorLog.WriteToErrorLog("Playbacklabel :: OnPaint :: " & ex.ToString)
    End Try
  End Sub

  Private Function GetTimerString() As String
    If Me._trackTime.TotalSeconds <= 0.0R AndAlso Me._trackTimeAlt.TotalSeconds <= 0.0R AndAlso Me._trackLength.TotalSeconds <= 0.0R AndAlso Me._statusMsg.Length = 0 Then
      Return ""
    End If

    If Me._trackTimeAlt.TotalSeconds >= 0.0R Then
      If Me._trackTimeAlt.Days > 0 Then
        GetTimerString = String.Format("{0:00}:{1:00}:{2:00}:{3:00} / ", _
                                   Me._trackTimeAlt.Days, _
                                   Me._trackTimeAlt.Hours, _
                                   Me._trackTimeAlt.Minutes, _
                                   Me._trackTimeAlt.Seconds)
      ElseIf Me._trackTimeAlt.Hours > 0 Then
        GetTimerString = String.Format("{0:00}:{1:00}:{2:00} / ", _
                                   Me._trackTimeAlt.Hours, _
                                   Me._trackTimeAlt.Minutes, _
                                   Me._trackTimeAlt.Seconds)
      Else
        GetTimerString = String.Format("{0:00}:{1:00} / ", _
                                   Me._trackTimeAlt.Minutes, _
                                   Me._trackTimeAlt.Seconds)
      End If
    Else
      GetTimerString = ""
    End If

    If Me._trackLength.TotalSeconds > 0.0R AndAlso Me._trackTimeNeg.TotalSeconds >= 0 Then
      If Me._trackLength.Days > 0 Then
        GetTimerString &= String.Format("{0:00}:{1:00}:{2:00}:{3:00} (-{4:00}:{5:00}:{6:00}:{7:00})", _
                                   Me._trackTime.Days, _
                                   Me._trackTime.Hours, _
                                   Me._trackTime.Minutes, _
                                   Me._trackTime.Seconds, _
                                   Me._trackTimeNeg.Days, _
                                   Me._trackTimeNeg.Hours, _
                                   Me._trackTimeNeg.Minutes, _
                                   Me._trackTimeNeg.Seconds)
      ElseIf Me._trackLength.Hours > 0 Then
        GetTimerString &= String.Format("{0:00}:{1:00}:{2:00} (-{3:00}:{4:00}:{5:00})", _
                                   Me._trackTime.Hours, _
                                   Me._trackTime.Minutes, _
                                   Me._trackTime.Seconds, _
                                   Me._trackTimeNeg.Hours, _
                                   Me._trackTimeNeg.Minutes, _
                                   Me._trackTimeNeg.Seconds)
      Else
        GetTimerString &= String.Format("{0:00}:{1:00} (-{2:00}:{3:00})", _
                                   Me._trackTime.Minutes, _
                                   Me._trackTime.Seconds, _
                                   Me._trackTimeNeg.Minutes, _
                                   Me._trackTimeNeg.Seconds)
      End If
    Else
      If Me._trackTime.Days > 0 Then
        GetTimerString &= String.Format("{0:00}:{1:00}:{2:00}:{3:00}", _
                                   Me._trackTime.Days, _
                                   Me._trackTime.Hours, _
                                   Me._trackTime.Minutes, _
                                   Me._trackTime.Seconds)
      ElseIf Me._trackTime.Hours > 0 Then
        GetTimerString &= String.Format("{0:00}:{1:00}:{2:00}", _
                                   Me._trackTime.Hours, _
                                   Me._trackTime.Minutes, _
                                   Me._trackTime.Seconds)
      Else
        GetTimerString &= String.Format("{0:00}:{1:00}", _
                                   Me._trackTime.Minutes, _
                                   Me._trackTime.Seconds)
      End If
    End If
  End Function

  Public Shared Function GetFormattedTimerString(ByVal seconds As Integer, ByVal totalLengthSeconds As Integer) As String
    Dim tm As New TimeSpan(0, 0, seconds)
    Dim len As New TimeSpan(0, 0, totalLengthSeconds)
    If len.Days > 0 OrElse tm.Days > 0 Then
      Return String.Format("{0:00}:{1:00}:{2:00}:{3:00}", tm.Days, tm.Hours, tm.Minutes, tm.Seconds)
    ElseIf len.Hours > 0 OrElse tm.Hours > 0 Then
      Return String.Format("{0:00}:{1:00}:{2:00}", tm.Hours, tm.Minutes, tm.Seconds)
    Else
      Return String.Format("{0:00}:{1:00}", tm.Minutes, tm.Seconds)
    End If
  End Function

  Protected Overrides Sub OnPaintBackground(ByVal e As System.Windows.Forms.PaintEventArgs)
    ' Do nothing
  End Sub

  Protected Overrides Sub OnMouseDown(ByVal e As System.Windows.Forms.MouseEventArgs)
    If Me._smallMode Then
      Me.TimerScroll.Enabled = False
      Me._lastMousePosX = e.X
    ElseIf Me._largeDisplayHeight > 0 Then
      Me.TimerScroll.Enabled = False
      Me._lastMousePosY = e.Y
    End If

    MyBase.OnMouseDown(e)
  End Sub

  Protected Overrides Sub OnMouseMove(ByVal e As System.Windows.Forms.MouseEventArgs)
    If Me._smallMode Then
      Me._smallModeOffset += e.X - Me._lastMousePosX
      Me._lastMousePosX = e.X
      Me.InvalidateSmart()
    ElseIf Me._largeDisplayHeight > 0 Then
      Me._largeDisplayOffset += e.Y - Me._lastMousePosY
      Me._lastMousePosY = e.Y
      Me.DoVerticalScrollBoundsCheck()
      Me.InvalidateSmart()
    End If

    MyBase.OnMouseMove(e)
  End Sub

  Protected Overrides Sub OnMouseUp(ByVal e As System.Windows.Forms.MouseEventArgs)
    If Me._smallMode Then
      Me._smallModeOffset += e.X - Me._lastMousePosX
      Me._lastMousePosX = e.X
      Me.ResetScrollTimer(False)
      Me.InvalidateSmart()
    ElseIf Me._largeDisplayHeight > 0 Then
      Me._largeDisplayOffset += e.Y - Me._lastMousePosY
      Me._lastMousePosY = e.Y
      Me.DoVerticalScrollBoundsCheck()
      Me.ResetScrollTimer(False)
      Me.InvalidateSmart()
    End If

    MyBase.OnMouseUp(e)
  End Sub

  Protected Overrides Sub OnDoubleClick(ByVal e As System.EventArgs)
    Me.SmallMode = Not Me._smallMode
    MyBase.OnDoubleClick(e)
  End Sub

  Private Function PixelsToPoints(ByVal pixels As Integer) As Single
    ' Starting guess using algorithm; this will be too large because of padding for multiline text
    Dim pt As Single = CSng(Math.Floor((72.0R * CDbl(pixels)) / CDbl(Me.AutoScaleDimensions.Height)))
    Dim bmp As New Bitmap(pixels * 10, pixels * 2)
    Dim rect As New Rectangle(0, 0, bmp.Width, bmp.Height)
    Dim g As Graphics = Graphics.FromImage(bmp)
    Dim fnt As Font
    Dim doLoop As Boolean = True

    ' Reduce the font size by 0.2 until we get a font that fits within the pixel size requested
    Do
      fnt = New Font(Me.Font.Name, pt, FontStyle.Regular)
      If (MeasureString(g, "Pp", rect, fnt).Height > pixels) Then
        pt -= 0.5F
      Else
        doLoop = False
      End If
      fnt.Dispose()
    Loop While doLoop OrElse pt <= 1.0F

    g.Dispose()
    bmp.Dispose()

    Return pt
  End Function

  Public Sub InvalidateSmart()
    Me.TimerInvalidate.Enabled = True
  End Sub

  Private Sub TimerInvalidate_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TimerInvalidate.Tick
    Me.TimerInvalidate.Enabled = False
    Me.Invalidate()
  End Sub

  Private Sub TimerScroll_Tick(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TimerScroll.Tick
    If Me._smallMode = True Then
      Me.TimerScroll.Interval = 15
      Me._smallModeOffset -= 2 * Me._dpiFactor
    Else
      If Me._largeDisplayScrollDirectionDown Then
        Me._largeDisplayOffset -= 2 * Me._dpiFactor
      Else
        Me._largeDisplayOffset += 2 * Me._dpiFactor
      End If

      If Me.DoVerticalScrollBoundsCheck() Then
        Me.TimerScroll.Interval = 5000
      Else
        Me.TimerScroll.Interval = 20
      End If
    End If

    Me.Invalidate()
  End Sub

  Private Function DoVerticalScrollBoundsCheck() As Boolean
    If Me._largeDisplayOffset < Me._topheight - Me._largeDisplayHeight Then
      Me._largeDisplayOffset = Me._topheight - Me._largeDisplayHeight
      Me._largeDisplayScrollDirectionDown = False
      Return True
    ElseIf Me._largeDisplayOffset > 0 Then
      Me._largeDisplayOffset = 0
      Me._largeDisplayScrollDirectionDown = True
      Return True
    End If

    Return False
  End Function
End Class
